%%% Copyright 2010 Google Inc.
%%%
%%% Licensed under the Apache License, Version 2.0 (the "License");
%%% you may not use this file except in compliance with the License.
%%% You may obtain a copy of the License at
%%%
%%%     http://www.apache.org/licenses/LICENSE-2.0
%%%
%%% Unless required by applicable law or agreed to in writing, software
%%% distributed under the License is distributed on an "AS IS" BASIS,
%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%%% See the License for the specific language governing permissions and
%%% limitations under the License.

%%% @copyright 2010 Google Inc. All Rights Reserved.
%%% @author <jacoby@google.com> Jacoby Thwaites [http://sites/madcloud]
%%% @version {@version}
%%% @doc
%%% This module is the gen_server that serves prog: module functions.
%%% It calls back to the implementation module whose name is supplied
%%% at startup.
%%% @see start_link/1
%%% @headerfile "../include/txm.hrl"

-module(prog_srv).

%%% See [http://www.erlang.org/doc/man/gen_server.html]
-behaviour(gen_server).

%%% Server state record name.
-define(STATE, state).

%%% Include record definitions and constants
-include("txm.hrl").

%%% ---------------------------------------------------------------------------
%%% Exported functions
%%% ---------------------------------------------------------------------------

%% Start and stop the server
-export([start_link/1, stop/0]).

%%% Client functions delegated to implementation
%%% Client functions that will always succeed return values directly.
-export([get_root_ref/0, record_to_ref/1]).
-export([delete_ref/2]).

%%% Client functions that might not succeed return ok or error tuples.
%%% This includes all functions that take a string path.
-export([get_root_ref/1, get_root_ref/2]).
-export([resolve_absolute/1, resolve_relative/2, resolve_scoped/2]).
-export([get_record/1]).

-export([new_record/3]).
-export([snapshot/1, snapshot/2]).

-export([get_shallow_index/1, get_deep_index/1]).
-export([get_shallow_contents/1, get_deep_contents/1]).

-export([import_file/2, import_binary/2]).

%%% Gen_server callback functions
-export([init/1, terminate/2, handle_call/3]).
-export([code_change/3, handle_cast/2, handle_info/2]).

%%% ---------------------------------------------------------------------------
%%% Record definitions
%%% ---------------------------------------------------------------------------

%% Server state is used to hold the implementation module.
-record(?STATE, {impl :: atom()}).

%%% ---------------------------------------------------------------------------
%%% Initialization, start and stop functions
%%% ---------------------------------------------------------------------------

%% @doc Starts the server using the supplied implementation module.
%%
%% The server is linked to the calling process.
%%
%% The name of the required implementation module (eg `prog' or `prog_mock')
%% must be supplied.
%% @spec start_link(atom()) -> {ok, pid()} | {error, term()}
start_link(Impl) ->
  State = #?STATE{impl = Impl},
  gen_server:start_link({local, ?MODULE}, ?MODULE, State, []).

%% @doc Stops the server. Only used from shell.
stop() ->
  gen_server:call(?MODULE, stop).

%%% ---------------------------------------------------------------------------
%%% Client functions
%%% ---------------------------------------------------------------------------

%% @see prog:get_root_ref/0
get_root_ref() ->
  gen_server:call(?MODULE, {get_root_ref, []},
                  ?SYNC_CALL_TIMEOUT).

%% @see prog:get_root_ref/1
get_root_ref(OpRef) ->
  gen_server:call(?MODULE, {get_root_ref, [OpRef]},
                  ?SYNC_CALL_TIMEOUT).

%% @see prog_get_root_ref/2
get_root_ref(OpRef, Version) ->
  gen_server:call(?MODULE, {get_root_ref, [OpRef, Version]},
                  ?SYNC_CALL_TIMEOUT).

%% @see prog:record_to_ref/1
record_to_ref(Record) ->
  gen_server:call(?MODULE, {record_to_ref, [Record]},
                  ?SYNC_CALL_TIMEOUT).

%% @see prog:resolve_absolute/1
resolve_absolute(Path) ->
  gen_server:call(?MODULE, {resolve_absolute, [Path]},
                  ?SYNC_CALL_TIMEOUT).

%% @see prog:resolve_relative/2
resolve_relative(RootRef, Path) ->
  gen_server:call(?MODULE, {resolve_relative, [RootRef, Path]},
                  ?SYNC_CALL_TIMEOUT).

%% @see prog:resolve_scoped/2
resolve_scoped(FolderRef, Name) ->
  gen_server:call(?MODULE, {resolve_scoped, [FolderRef, Name]},
                  ?SYNC_CALL_TIMEOUT).

%% @see prog:delete_ref/2
delete_ref(RootRef, Path) ->
  gen_server:call(?MODULE, {delete_ref, [RootRef, Path]},
                  ?SYNC_CALL_TIMEOUT).

%% @see prog:get_record/1
get_record(Ref) ->
  try
    gen_server:call(?MODULE, {get_record, [Ref]},
                    ?SYNC_CALL_TIMEOUT)
  catch
    _:Reason -> ?ERROR_RETURN(Reason)
  end.

%% @see prog:get_shallow_contents/1
get_shallow_contents(FolderRef) ->
  try
    gen_server:call(?MODULE, {get_shallow_contents, [FolderRef]},
                    ?SYNC_CALL_TIMEOUT)
  catch
    _:Reason -> ?ERROR_RETURN(Reason)
  end.

%% @see prog:get_depp_contents/1
get_deep_contents(FolderRef) ->
  try
    gen_server:call(?MODULE, {get_deep_contents, [FolderRef]},
                    ?SYNC_CALL_TIMEOUT)
  catch
    _:Reason -> ?ERROR_RETURN(Reason)
  end.

%% @see prog:get_shallow_index/1
get_shallow_index(FolderRef) ->
  try
    gen_server:call(?MODULE, {get_shallow_index, [FolderRef]},
                    ?SYNC_CALL_TIMEOUT)
  catch
    _:Reason -> ?ERROR_RETURN(Reason)
  end.

%% @see prog:get_deep_index/1
get_deep_index(FolderRef) ->
  try
    gen_server:call(?MODULE, {get_deep_index, [FolderRef]},
                    ?SYNC_CALL_TIMEOUT)
  catch
    _:Reason -> ?ERROR_RETURN(Reason)
  end.

%% @see prog:new_record/3
new_record(ParentRef, Name, Record) ->
  gen_server:call(?MODULE, {new_record, [ParentRef, Name, Record]},
                  ?SYNC_CALL_TIMEOUT).

%% @see prog:snapshot/1
snapshot(OpRef) ->
  gen_server:call(?MODULE, {snapshot, [OpRef]},
                  ?SYNC_CALL_TIMEOUT).

%% @see prog:snapshot/2
snapshot(OpRef, VsnName) ->
  gen_server:call(?MODULE, {snapshot, [OpRef, VsnName]},
                  ?SYNC_CALL_TIMEOUT).

%% @see prog:import_file/2
import_file(Root, Filename) ->
  gen_server:call(?MODULE, {import_file, [Root, Filename]},
                  ?SYNC_CALL_TIMEOUT).

%% @see prog:import_binary/2
import_binary(Root, Binary) ->
  gen_server:call(?MODULE, {import_binary, [Root, Binary]},
                  ?SYNC_CALL_TIMEOUT).

%%% ---------------------------------------------------------------------------
%%% Gen_server callback functions
%%% ---------------------------------------------------------------------------

%% Calls back to implementation init function. Stops server if error
%% return from implementation.
%% @private
init(#?STATE{impl = Impl} = State) ->
  ?INFO([init, {impl, Impl}]),
  process_flag(trap_exit, true),

  case Impl:init() of
    ?OK_RETURN ->
      ?INFO([{inited, ?TABLES}]),
      ?OK_RETURN(State);
    ?ERROR_RETURN(Reason) ->
      ?INFO([stop, {missing, Reason}]),
      {stop, Reason}
  end.

%% No action on terminate
%% @private
terminate(Reason, _State) ->
  ?INFO([{terminate, Reason}]),
  ?OK_RETURN.

%% Stops the server. Not used except from command line, normally
%% the supervisor causes termination callback.
%% @private
handle_call(stop, _From, State) ->
  {stop, ?NORMAL_EXIT, ?OK_RETURN(State), State};

%% Handles all calls expected to return a reply (either ok or error).
handle_call({Function, Args}, _From, #?STATE{impl = Module} = State) ->
  ?DEBUG([Function]),
  {reply, apply(Module, Function, Args), State}.

%%% --------------------------------------------------------------------------
%%% Unimplemented behaviour callbacks.
%%% --------------------------------------------------------------------------

code_change(_, _, _) -> throw(?REASON_NOT_IMPLEMENTED).
handle_cast(_, _) -> throw(?REASON_NOT_IMPLEMENTED).
handle_info(_, _) -> throw(?REASON_NOT_IMPLEMENTED).
